# -*- coding: utf-8 -*-
"""
mysqldiff.py - 数据库结构差异对比与同步工具

功能：
- 对比两个 MySQL 数据库的表结构差异
- 支持表/字段重命名、新增、修改、删除
- 支持外键约束同步
- 支持通过 map.config 配置表/字段映射关系（重命名或数据迁移）
- 输出差异 SQL 到文件，或直接执行
- 可选是否执行 SQL（-x 参数）
- 新增 --no-fk 参数：跳过所有外键相关操作（禁用检查、增删外键、启用检查）

命令格式：
    python mysqldiff.py [options] mapping

示例命令：

1. 基本用法：对比结构并输出 SQL 到文件
    python mysqldiff.py --s=root:root@localhost:3306 --file=diff.sql db_new:db_old

2. 对比并立即执行差异 SQL（不生成文件）
    python mysqldiff.py --s=root:root@localhost:3306 -x db_new:db_old

3. 跳过外键操作，输出差异到文件
    python mysqldiff.py --s=root:root@localhost:3306 --no-fk --file=diff.sql db_new:db_old

4. 使用不同账号连接新旧库（分别指定）
    python mysqldiff.py --sn=root:newpass@192.168.1.10:3306 --so=backup:123@192.168.1.20:3306 --file=diff.sql db_new:db_old

5. 对比指定表（可选）
    python mysqldiff.py --s=root:root@localhost:3306 --file=diff.sql db_new.table1:db_old.table2

6. 启用执行 + 数据拷贝（INSERT）
    python mysqldiff.py --s=root:root@localhost:3306 -x -c --file=diff_with_data.sql db_new:db_old

7. 仅对比结构，不输出文件（打印到控制台）
    python mysqldiff.py --s=root:root@localhost:3306 db_new:db_old


参数说明：
    -x                  : 是否立即执行生成的 SQL 语句（执行模式）
    -c                  : 是否拷贝新库中的默认数据到旧库（配合 -x 使用，INSERT INTO ... SELECT）
    --no-fk             : 跳过所有外键操作（不禁止检查、不增删外键、不禁用检查）
    --s S               : 新旧库共用连接配置（格式：user:pass@host:port）
    --sn SN             : 新数据库连接配置（source new）
    --so SO             : 旧数据库连接配置（source old）
    --file FILE         : 差异 SQL 输出文件路径（可选，不指定则输出到控制台）
    --no-redirect       : 禁用输出重定向到日志文件（GUI模式下使用）
    mapping             : 指定要对比的数据库（或表），格式为：db1:db2 或 db1.tbl1:db2.tbl2

映射配置文件 map.config 示例：
    user_info -> user                    # 表重命名
    user.name -> user.username           # 字段重命名
    user.[id, name] -> user.[user_id, username]  # 多字段映射
    old_table => new_table               # 数据迁移（INSERT INTO ... SELECT）

注意：
- 所有选项参数（如 --s, --file）必须在最后的 mapping 参数（db_new:db_old）之前。
- 连接字符串格式：用户名:密码@主机:端口（如 root:123456@localhost:3306）
- 若未指定 --file，则差异 SQL 将输出到标准输出（屏幕）
"""

import sys
import os

# 禁用GUI以防止在打包后环境中创建意外的GUI窗口
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'
os.environ['SDL_VIDEODRIVER'] = 'dummy'
os.environ['TK_SILENCE_DEPRECATION'] = '1'
# 额外禁用可能触发GUI的库
os.environ['DISPLAY'] = ''
os.environ['QT_QPA_PLATFORM'] = 'offscreen'
os.environ['PYQTGRAPH_QT_LIB'] = 'None'

# 添加pymysql导入，修复打包后运行时的NameError错误
try:
    import pymysql
except ImportError as e:
    print(f"[ERROR] 无法导入pymysql库: {e}")
    print("[ERROR] 请确保已安装pymysql库: pip install pymysql")
    sys.exit(1)

import traceback
import datetime


# 添加全局异常处理器，以便捕获所有异常
def handle_exception(exc_type, exc_value, exc_traceback):
    print(f"[全局异常] 类型: {exc_type.__name__}")
    print(f"[全局异常] 值: {exc_value}")
    print(f"[全局异常] 追踪:")
    traceback.print_tb(exc_traceback)

    # 检查是否是NoneType错误
    if exc_type == AttributeError and "'NoneType' object has no attribute 'strip'" in str(exc_value):
        print("[全局异常] 检测到NoneType.strip错误，尝试找到问题位置")
        # 打印堆栈跟踪的详细信息
        for frame_info in traceback.extract_tb(exc_traceback):
            print(f"[全局异常] 文件: {frame_info.filename}")
            print(f"[全局异常] 行号: {frame_info.lineno}")
            print(f"[全局异常] 函数: {frame_info.name}")
            print(f"[全局异常] 代码: {frame_info.line}")


# 设置全局异常处理器
sys.excepthook = handle_exception

# 在GUI模式下重定向标准输出和错误输出到日志文件
# 但允许通过 --no-redirect 参数禁用此行为
no_redirect = '--no-redirect' in sys.argv
# 检查是否通过GUI调用（通过检查命令行参数中是否有GUI特定的参数）
is_gui_call = any('--file=' in arg for arg in sys.argv)

# 添加更详细的调试信息
print(f"[DEBUG] 启动mysqldiff.py，当前工作目录: {os.getcwd()}")
print(f"[DEBUG] sys.argv: {sys.argv}")
print(f"[DEBUG] no_redirect: {no_redirect}")
print(f"[DEBUG] is_gui_call: {is_gui_call}")
print(f"[DEBUG] getattr(sys, 'frozen', False): {getattr(sys, 'frozen', False)}")
if hasattr(sys, '_MEIPASS'):
    print(f"[DEBUG] _MEIPASS: {sys._MEIPASS}")

# 创建一个临时日志文件来记录错误
temp_log_path = None
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
    temp_log_path = os.path.join(sys._MEIPASS, 'mysqldiff_temp.log')
    print(f"[DEBUG] 创建临时日志文件: {temp_log_path}")
    try:
        temp_log_file = open(temp_log_path, 'w', encoding='utf-8')
        temp_log_file.write("[DEBUG] 临时日志文件创建成功\n")
        temp_log_file.flush()
    except Exception as e:
        print(f"[ERROR] 无法创建临时日志文件: {e}")

if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS') and not no_redirect and not is_gui_call:
    # 打包后的exe模式，且不是通过GUI调用
    log_file_path = os.path.join(os.path.dirname(sys.executable), 'mysqldiff.log')
    print(f"[DEBUG] 重定向输出到日志文件: {log_file_path}")
    try:
        sys.stdout = open(log_file_path, 'w', encoding='utf-8')
        sys.stderr = sys.stdout
        print(f"[DEBUG] 成功重定向输出到日志文件")
    except Exception as e:
        print(f"[ERROR] 无法重定向输出到日志文件: {e}")
elif no_redirect:
    # 如果明确指定了 --no-redirect 参数，确保不重定向输出
    print("[DEBUG] 检测到 --no-redirect 参数，不重定向输出")
elif is_gui_call:
    # 如果是通过GUI调用，确保不重定向输出
    print("[DEBUG] 检测到GUI调用，不重定向输出")

# 全局配置
MAX_ALLOWED_PACKET = 1048576  # MySQL max_allowed_packet 默认值
# 使用绝对路径以避免打包后路径问题
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
    # 在PyInstaller打包环境中
    # 使用 sys.argv[0] 获取当前执行脚本的路径，更可靠
    if sys.argv[0].endswith('mysqldiff.py'):
        # 如果是通过其他脚本(如gui.py)调用mysqldiff.py
        script_dir = os.path.dirname(sys.argv[0])
    else:
        # 直接运行打包后的exe
        script_dir = os.path.dirname(sys.executable)
    DIFF_FILE_PATH = os.path.join(script_dir, 'diff.sql')
    EXEC_LOG_PATH = os.path.join(script_dir, 'error.log')
    MAP_CONFIG_PATH = os.path.join(script_dir, 'map.config')
else:
    DIFF_FILE_PATH = 'diff.sql'
    EXEC_LOG_PATH = 'error.log'
    MAP_CONFIG_PATH = 'map.config'

# 添加路径调试信息
print(f"[DEBUG] 当前执行模式: {'打包后' if getattr(sys, 'frozen', False) else '源码'}")
print(f"[DEBUG] sys.argv[0]: {sys.argv[0]}")
print(f"[DEBUG] sys.executable: {sys.executable}")
print(f"[DEBUG] 当前工作目录: {os.getcwd()}")
if hasattr(sys, '_MEIPASS'):
    print(f"[DEBUG] _MEIPASS: {sys._MEIPASS}")
print(f"[DEBUG] 默认diff.sql路径: {DIFF_FILE_PATH}")
print(f"[DEBUG] 默认map.config路径: {MAP_CONFIG_PATH}")
print(f"[DEBUG] map.config文件是否存在: {os.path.exists(MAP_CONFIG_PATH)}")


# 替换 web.py 的 safestr 功能（web.py 不再维护，避免依赖）
def safestr(obj):
    """
    安全地将任意对象转换为字符串并进行 SQL 转义，防止注入
    """
    if obj is None:
        return 'NULL'
    elif obj is True:
        return "'1'"
    elif obj is False:
        return "'0'"
    elif isinstance(obj, (int, float)):
        return str(obj)
    elif isinstance(obj, str):
        # 转义单引号、反斜杠
        escaped = obj.replace("\\", "\\\\").replace("'", "\\'")
        return f"'{escaped}'"
    elif isinstance(obj, datetime.datetime):
        return f"'{obj.strftime('%Y-%m-%d %H:%M:%S')}'"
    else:
        return repr(str(obj))


def q(s):
    """用括号包围字符串"""
    return f"({s})"


class Parameter:
    """
    存储数据库连接参数：用户、密码、主机、端口、数据库名、表名
    """

    def __init__(self):
        self.user = None
        self.pw = None
        self.host = None
        self.port = 3306
        self.name = None
        self.table = None

    def build(self, conn_str):
        """
        解析连接字符串：user:pass@host:port
        """
        try:
            # 添加None值检查
            if conn_str is None:
                raise ValueError("连接字符串不能为None")

            auth, addr = conn_str.split('@')
            user, pw = auth.split(':')
            host, port = addr.split(':')

            # 添加分割后的None值检查
            if user is None:
                user = ""
            if pw is None:
                pw = ""
            if host is None:
                host = ""
            if port is None:
                port = "3306"

            self.user = user.strip() if user else ""
            self.pw = pw.strip() if pw else ""
            self.host = host.strip() if host else ""
            self.port = int(port.strip()) if port else 3306
        except Exception as e:
            raise ValueError(f"连接字符串格式错误: {conn_str}, 错误: {e}")

    def valid(self):
        return all([self.user, self.pw, self.host, self.port, self.name])

    def __str__(self):
        return f"User={self.user}, Host={self.host}, Port={self.port}, DB={self.name}, Table={self.table}"


def connect_mysql(host, db, user, pw, port=3306):
    """
    使用 pymysql 连接 MySQL 数据库
    """
    try:
        conn = pymysql.connect(
            host=host,
            port=int(port),
            user=user,
            password=pw,
            database=db,
            charset='utf8mb4',
            autocommit=False  # 手动控制事务
        )
        return conn
    except Exception as e:
        error_msg = f"数据库连接失败: {e}"
        print(f"[ERROR] {error_msg}")
        # 提供更友好和具体的错误提示
        if "Access denied" in str(e):
            print("[ERROR] 用户名或密码错误")
            print("  请检查以下信息是否正确:")
            print(f"    - 用户名: {user}")
            print(f"    - 密码: ***** (出于安全考虑不显示具体密码)")
            print(f"    - 主机: {host}")
            print(f"    - 端口: {port}")
            print(f"    - 数据库: {db}")
            print("  提示: 确保数据库用户具有访问指定数据库的权限")
        elif "Can't connect" in str(e):
            print("[ERROR] 无法连接到数据库服务器")
            print("  请检查以下信息是否正确:")
            print(f"    - 主机地址: {host}")
            print(f"    - 端口号: {port}")
            print("  提示: 确保数据库服务正在运行且网络连接正常")
        elif "Unknown database" in str(e):
            print("[ERROR] 数据库不存在")
            print(f"  无法找到数据库: {db}")
            print("  提示: 请确认数据库名称是否正确，或者先创建该数据库")
        else:
            print(f"[ERROR] 连接详情: user={user}, host={host}, port={port}, database={db}")
        # 尝试记录到临时日志文件
        try:
            temp_log_file_path = os.path.join(os.getcwd(), 'error.log')
            with open(temp_log_file_path, 'a', encoding='utf-8') as temp_log_file:
                temp_log_file.write(f"[ERROR] {error_msg}\n")
                temp_log_file.write(f"[ERROR] 连接详情: user={user}, host={host}, port={port}, database={db}\n")
                import traceback
                temp_log_file.write(f"[ERROR] 详细追踪信息:\n")
                traceback.print_exc(file=temp_log_file)
                temp_log_file.write("\n")
                temp_log_file.flush()
        except:
            pass  # 防止在写入日志时出错
        sys.exit(1)


class MapParser:
    """
    解析 map.config 中的映射规则：
        表重命名：old_table -> new_table
        字段重命名：old_table.col -> new_table.col
        多字段映射：old.[a,b] -> new.[x,y]
        数据迁移：old => new
    """

    def __init__(self):
        self.old_table = None
        self.new_table = None
        self.old_cols = []
        self.new_cols = []
        self.map_data = False  # 是否为数据迁移（=>）
        self.line = None

    def parse(self, line):
        # 添加None值检查
        if line is None:
            return False

        line = line.strip()
        if not line or line.startswith('#'):
            return False

        if '=>' in line:
            self.map_data = True
            old_part, new_part = line.split('=>', 1)
        elif '->' in line:
            self.map_data = False
            old_part, new_part = line.split('->', 1)
        else:
            raise SyntaxError(f"格式错误，缺少 '->' 或 '=>': {line}")

        # 添加分割后的None值检查
        if old_part is None:
            old_part = ""
        if new_part is None:
            new_part = ""

        old_part = old_part.strip()
        new_part = new_part.strip()

        # 表级重命名
        if '.' not in old_part and '.' not in new_part:
            self.old_table = old_part
            self.new_table = new_part
        # 字段级映射
        elif '[' in old_part and '[' in new_part:
            # 多字段映射：tbl.[a,b] -> tbl.[x,y]
            ot, oc_str = old_part.split('.', 1)
            nt, nc_str = new_part.split('.', 1)

            # 添加分割后的None值检查
            if ot is None:
                ot = ""
            if oc_str is None:
                oc_str = ""
            if nt is None:
                nt = ""
            if nc_str is None:
                nc_str = ""

            self.old_table = ot.strip() if ot else ""
            self.new_table = nt.strip() if nt else ""

            # 处理字段列表
            oc_content = oc_str.strip('[]') if oc_str else ""
            nc_content = nc_str.strip('[]') if nc_str else ""

            self.old_cols = [c.strip() if c else "" for c in oc_content.split(',')] if oc_content else []
            self.new_cols = [c.strip() if c else "" for c in nc_content.split(',')] if nc_content else []
        else:
            # 单字段：tbl.col -> tbl.col
            ot, oc = old_part.split('.', 1)
            nt, nc = new_part.split('.', 1)

            # 添加分割后的None值检查
            if ot is None:
                ot = ""
            if oc is None:
                oc = ""
            if nt is None:
                nt = ""
            if nc is None:
                nc = ""

            self.old_table = ot.strip() if ot else ""
            self.new_table = nt.strip() if nt else ""
            self.old_cols = [oc.strip() if oc else ""]
            self.new_cols = [nc.strip() if nc else ""]

        self.line = line
        self._validate()
        return True

    def _validate(self):
        if self.map_data and self.old_table == self.new_table:
            raise ValueError(f"数据迁移时表名不能相同: {self.line}")
        if len(self.old_cols) != len(self.new_cols):
            raise ValueError(f"新旧字段数量不一致: {self.line}")

    def build_rename_sql(self):
        """生成重命名 SQL（ALTER TABLE / CHANGE COLUMN）"""
        sqls = []
        # 字段重命名
        if self.old_cols:
            for i, old_col in enumerate(self.old_cols):
                new_col = self.new_cols[i]
                sqls.append(f"ALTER TABLE `{self.old_table}` CHANGE `{old_col}` `{new_col}`;")
        # 表重命名
        if self.old_table != self.new_table:
            sqls.append(f"ALTER TABLE `{self.old_table}` RENAME TO `{self.new_table}`;")
        return sqls

    def build_pipe_sql(self, db_old, db_new):
        """生成数据迁移 SQL（INSERT INTO ... SELECT）"""
        if self.old_table == self.new_table:
            return []

        old_keys = ', '.join([f"`{c}`" for c in self.old_cols]) if self.old_cols else '*'
        new_keys = ', '.join([f"`{c}`" for c in self.new_cols]) if self.new_cols else old_keys.replace(
            f"`{self.old_table}`.", "")

        try:
            with db_new.cursor() as cursor:
                cursor.execute(f"SELECT {old_keys} FROM `{self.old_table}`")
                rows = cursor.fetchall()
                if not rows:
                    return []

                columns = [desc[0] for desc in cursor.description]
                values_list = []
                packet_size = 0
                sqls = []

                for row in rows:
                    values = ', '.join([safestr(v) for v in row])
                    values_list.append(f"({values})")
                    packet_size += len(values)

                    if packet_size > MAX_ALLOWED_PACKET / 2:
                        sqls.append(
                            f"INSERT INTO `{self.new_table}` ({new_keys}) VALUES\n" + ",\n".join(values_list) + ";")
                        values_list = []
                        packet_size = 0

                if values_list:
                    sqls.append(f"INSERT INTO `{self.new_table}` ({new_keys}) VALUES\n" + ",\n".join(values_list) + ";")
                return sqls
        except Exception as e:
            print(f"数据迁移失败 {self.line}: {e}")
            # 尝试记录到临时日志文件
            if 'temp_log_file' in locals():
                temp_log_file.write(f"数据迁移失败 {self.line}: {e}\n")
                temp_log_file.flush()
            return []


def parse_map_config(config_path, db_old_conn, db_new_conn, no_redirect=True):
    """解析 map.config 文件，返回重命名和数据迁移的解析器列表"""
    if not no_redirect:
        print(f"[DEBUG] 尝试解析配置文件: {config_path}")
        print(f"[DEBUG] 配置文件是否存在: {os.path.exists(config_path)}")

    if not os.path.exists(config_path):
        if not no_redirect:
            print(f"[DEBUG] 配置文件不存在，返回空列表")
        return [], []

    rename_parsers = []
    pipe_parsers = []

    try:
        with open(config_path, 'r', encoding='utf-8') as f:
            if not no_redirect:
                print(f"[DEBUG] 成功打开配置文件")
            for line_num, line in enumerate(f, 1):
                if not no_redirect:
                    print(f"[DEBUG] 读取第 {line_num} 行: {repr(line)}")

                # 添加None值检查
                if line is None:
                    if not no_redirect:
                        print(f"[警告] 第 {line_num} 行为None，跳过")
                    continue

                line = line.strip()
                if not no_redirect:
                    print(f"[DEBUG] 第 {line_num} 行处理后: {repr(line)}")

                if not line or line.startswith('#'):
                    continue

                parser = MapParser()
                try:
                    if parser.parse(line):
                        if parser.map_data:
                            pipe_parsers.append(parser)
                            if not no_redirect:
                                print(f"[DEBUG] 添加数据迁移解析器: {line}")
                        else:
                            rename_parsers.append(parser)
                            if not no_redirect:
                                print(f"[DEBUG] 添加重命名解析器: {line}")
                except Exception as e:
                    if not no_redirect:
                        print(f"[警告] 解析 map.config 第 {line_num} 行失败: {line}")
                        print(f"错误: {e}")
                        import traceback
                        traceback.print_exc()
                    # 尝试记录到临时日志文件
                    if 'temp_log_file' in locals():
                        temp_log_file.write(f"[警告] 解析 map.config 第 {line_num} 行失败: {line}\n错误: {e}\n")
                        temp_log_file.flush()

        if not no_redirect:
            print(f"[DEBUG] 配置文件解析完成，重命名解析器: {len(rename_parsers)}, 数据迁移解析器: {len(pipe_parsers)}")
    except Exception as e:
        if not no_redirect:
            print(f"[错误] 读取配置文件失败: {e}")
            import traceback
            traceback.print_exc()
        # 尝试记录到临时日志文件
        if 'temp_log_file' in locals():
            temp_log_file.write(f"[错误] 读取配置文件失败: {e}\n")
            temp_log_file.flush()
        return [], []

    return rename_parsers, pipe_parsers


def get_table_structure(conn, table):
    """获取建表语句"""
    with conn.cursor() as cursor:
        cursor.execute(f"SHOW CREATE TABLE `{table}`")
        result = cursor.fetchone()
        return result[1] + ";" if result else ""


def exists_table(conn, table):
    """判断表是否存在"""
    with conn.cursor() as cursor:
        cursor.execute("SHOW TABLES LIKE %s", (table,))
        return cursor.fetchone() is not None


def get_table_columns(conn, table):
    """获取表字段信息"""
    with conn.cursor(pymysql.cursors.DictCursor) as cursor:
        cursor.execute(f"DESCRIBE `{table}`")
        return cursor.fetchall()


def get_foreign_keys(conn, db_name):
    """获取数据库所有外键约束"""
    query = """
    SELECT 
        CONSTRAINT_NAME, TABLE_NAME, COLUMN_NAME,
        REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME,
        UPDATE_RULE, DELETE_RULE
    FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE K
    JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS R
        ON K.CONSTRAINT_NAME = R.CONSTRAINT_NAME
    WHERE K.TABLE_SCHEMA = %s AND K.REFERENCED_TABLE_NAME IS NOT NULL
    """
    with conn.cursor(pymysql.cursors.DictCursor) as cursor:
        cursor.execute(query, (db_name,))
        return cursor.fetchall()


def build_alter_table_sql(fields_new, fields_old):
    """对比字段，生成 ALTER TABLE 语句"""
    sqls = []
    old_dict = {f['Field']: f for f in fields_old}
    new_dict = {f['Field']: f for f in fields_new}

    for field_name, new_field in new_dict.items():
        old_field = old_dict.get(field_name)
        if not old_field:
            # 新增字段
            null_clause = "NULL" if new_field['Null'] == 'YES' else "NOT NULL"
            default_clause = f"DEFAULT {safestr(new_field['Default'])}" if new_field['Default'] is not None else ""
            extra = new_field['Extra']
            sqls.append(f"ADD COLUMN `{field_name}` {new_field['Type']} {null_clause} {default_clause} {extra}")
        else:
            # 修改字段（类型、默认值、非空等）
            # 修复：确保在所有需要的地方都正确处理 DEFAULT 子句
            needs_modify = False
            modify_parts = [f"`{field_name}`", new_field['Type']]

            if new_field['Null'] != old_field['Null']:
                if new_field['Null'] == 'YES':
                    modify_parts.append("NULL")
                else:
                    modify_parts.append("NOT NULL")
                needs_modify = True

            # 修复：处理 DEFAULT 值的变化，包括从有到无
            if new_field['Default'] != old_field['Default']:
                if new_field['Default'] is not None:
                    modify_parts.append(f"DEFAULT {safestr(new_field['Default'])}")
                else:
                    # 如果新字段没有默认值，需要显式 DROP DEFAULT
                    modify_parts.append("DROP DEFAULT")
                needs_modify = True
            elif new_field['Default'] is not None:
                # 如果默认值没变但存在，也加上，因为 MODIFY 需要完整定义
                modify_parts.append(f"DEFAULT {safestr(new_field['Default'])}")

            if new_field['Extra'] != old_field['Extra']:
                modify_parts.append(new_field['Extra'])
                needs_modify = True

            if needs_modify:
                sqls.append(f"MODIFY " + " ".join(modify_parts))

    # 删除旧字段
    for field_name in old_dict:
        if field_name not in new_dict:
            sqls.append(f"DROP COLUMN `{field_name}`")

    # 主键变更（简化处理）
    # 修复：如果主键变更，应该先 DROP 再 ADD
    pri_new = [f for f in fields_new if f['Key'] == 'PRI']
    pri_old = [f for f in fields_old if f['Key'] == 'PRI']

    pri_new_fields = [f['Field'] for f in pri_new]
    pri_old_fields = [f['Field'] for f in pri_old]

    if pri_new_fields != pri_old_fields:
        # 如果旧主键存在，先删除
        if pri_old_fields:
            sqls.append("DROP PRIMARY KEY")
        # 如果新主键存在，再添加
        if pri_new_fields:
            pk_cols = ', '.join([f"`{f}`" for f in pri_new_fields])
            sqls.append(f"ADD PRIMARY KEY ({pk_cols})")

    return sqls


def build_foreign_key_sqls(db_new_conn, db_new_name, db_old_conn, db_old_name):
    """对比外键，生成增删改 SQL"""
    fks_new = get_foreign_keys(db_new_conn, db_new_name)
    fks_old = get_foreign_keys(db_old_conn, db_old_name)

    fk_old_dict = {fk['CONSTRAINT_NAME']: fk for fk in fks_old}
    add_sqls = []
    drop_sqls = []

    for fk in fks_new:
        old_fk = fk_old_dict.pop(fk['CONSTRAINT_NAME'], None)
        if not old_fk:
            # 新增外键
            # 修复：确保表名和列名都被反引号包裹
            sql = (f"ALTER TABLE `{fk['TABLE_NAME']}` "
                   f"ADD CONSTRAINT `{fk['CONSTRAINT_NAME']}` "
                   f"FOREIGN KEY (`{fk['COLUMN_NAME']}`) "
                   f"REFERENCES `{fk['REFERENCED_TABLE_NAME']}`(`{fk['REFERENCED_COLUMN_NAME']}`) "
                   f"ON UPDATE {fk['UPDATE_RULE']} ON DELETE {fk['DELETE_RULE']};")
            add_sqls.append(sql)
        # TODO: 可加入外键修改逻辑（此处简化为仅增删）

    for fk in fk_old_dict.values():
        # 修复：确保表名和约束名都被反引号包裹
        drop_sqls.append(f"ALTER TABLE `{fk['TABLE_NAME']}` DROP FOREIGN KEY `{fk['CONSTRAINT_NAME']}`;")

    return drop_sqls, add_sqls


def write_sql_to_file(file_obj, title, sql_list):
    """将 SQL 写入文件"""
    # 优化：只有当 sql_list 非空时才写入
    if sql_list:
        file_obj.write('\n' + f"/* {title.center(76, '*')} */\n")
        for sql in sql_list:
            file_obj.write(sql + "\n")


def execute_sql(conn, sql_list, log_file):
    """执行 SQL 列表，出错写入日志"""
    # 优化：空列表直接返回
    if not sql_list:
        return
    cursor = conn.cursor()
    for sql in sql_list:
        try:
            cursor.execute(sql)
        except Exception as e:
            error_info = f"[ERROR] 执行失败:\n{sql}\n{e}\n"
            print(error_info)
            log_file.write(error_info)
            # 尝试记录到临时日志文件
            if 'temp_log_file' in locals():
                temp_log_file.write(error_info)
                temp_log_file.flush()
            raise


def main():
    """主函数"""
    try:
        from argparse import ArgumentParser
        parser = ArgumentParser(description="MySQL 数据库结构差异同步工具")
        parser.add_argument('mapping', help="新库:旧库，例如 db_new:db_old")
        parser.add_argument('--s', help="共用连接配置: user:pass@host:port")
        parser.add_argument('--sn', help="新库连接: user:pass@host:port")
        parser.add_argument('--so', help="旧库连接: user:pass@host:port")
        parser.add_argument('--file', default='diff.sql', help="输出 SQL 文件")
        parser.add_argument('-x', action='store_true', help="立即执行 SQL")
        parser.add_argument('-c', action='store_true', help="拷贝新库默认数据")
        parser.add_argument('--no-fk', action='store_true', dest='no_fk', help="跳过外键操作")
        parser.add_argument('--no-redirect', action='store_true', help="禁用输出重定向到日志文件（GUI模式下使用）")

        args = parser.parse_args()
        if not args.no_redirect:
            print(f"[DEBUG] 解析后的参数: {args}")
    except Exception as e:
        print(f"[ERROR] 命令行参数解析失败: {e}")
        print("请检查命令行参数是否正确")
        print("使用 --help 参数查看帮助信息")
        sys.exit(1)

    # 添加详细的路径调试信息
    if not args.no_redirect:
        print("[DEBUG] 接收到的文件路径参数: {}".format(args.file))
        print("[DEBUG] 规范化后的文件路径: {}".format(os.path.normpath(args.file)))
        print("[DEBUG] 绝对路径: {}".format(os.path.abspath(args.file)))
        print("[DEBUG] 当前工作目录: {}".format(os.getcwd()))

    # 处理打包环境中的路径问题 - 修复文件路径
    if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
        # 在打包环境中，规范化路径分隔符
        args.file = args.file.replace('\\', '/').replace('\\', '/')
        if not args.no_redirect:
            print("[DEBUG] 规范化路径分隔符后: {}".format(args.file))
            print("[DEBUG] 当前工作目录: {}".format(os.getcwd()))
            print("[DEBUG] sys.executable: {}".format(sys.executable))
            print("[DEBUG] _MEIPASS: {}".format(getattr(sys, '_MEIPASS', 'N/A')))

        # 关键修复：确保文件在正确的目录生成
        # 如果是通过GUI调用，确保文件在exe所在目录创建
        if 'mysqldiff_gui' in sys.executable or 'gui' in ' '.join(sys.argv).lower():
            exe_dir = os.path.dirname(sys.executable)
            # 如果路径是相对路径，则调整为exe目录
            if not os.path.isabs(args.file):
                filename = os.path.basename(args.file)
                args.file = os.path.join(exe_dir, filename).replace('\\', '/').replace('\\', '/')
                if not args.no_redirect:
                    print("[DEBUG] GUI调用，调整路径到exe目录: {}".format(args.file))
            # 如果是绝对路径但不在exe目录，则也调整到exe目录
            elif not args.file.startswith(exe_dir.replace('\\', '/').replace('\\', '/')):
                filename = os.path.basename(args.file)
                args.file = os.path.join(exe_dir, filename).replace('\\', '/').replace('\\', '/')
                if not args.no_redirect:
                    print("[DEBUG] GUI调用，调整绝对路径到exe目录: {}".format(args.file))
        else:
            # 其他情况使用当前工作目录
            if not os.path.isabs(args.file):
                args.file = os.path.join(os.getcwd(), args.file).replace('\\', '/').replace('\\', '/')
                if not args.no_redirect:
                    print("[DEBUG] 非GUI调用，使用工作目录: {}".format(args.file))

        # 确保路径是规范化的绝对路径
        args.file = os.path.abspath(args.file).replace('\\', '/').replace('\\', '/')
        if not args.no_redirect:
            print("[DEBUG] 最终规范化绝对路径: {}".format(args.file))

    # 确保输出目录存在
    output_dir = os.path.dirname(os.path.abspath(args.file))
    if not os.path.exists(output_dir):
        try:
            os.makedirs(output_dir, exist_ok=True)
            if not args.no_redirect:
                print("[DEBUG] 已创建输出目录: {}".format(output_dir))
        except Exception as e:
            print("[ERROR] 创建输出目录失败: {}".format(e))
            # 尝试记录到临时日志文件
            try:
                temp_log_path = os.path.join(os.getcwd(), 'error.log')
                with open(temp_log_path, 'a', encoding='utf-8') as temp_log_file:
                    temp_log_file.write(f"[ERROR] 创建输出目录失败: {e}\n")
                    import traceback
                    traceback.print_exc(file=temp_log_file)
                    temp_log_file.write("\n")
                    temp_log_file.flush()
            except:
                pass  # 防止在写入日志文件时出错
            sys.exit(1)

    # 添加调试信息
    if not args.no_redirect:
        print("[DEBUG] 最终将生成SQL文件到: {}".format(args.file))
        print("[DEBUG] 最终绝对路径: {}".format(os.path.abspath(args.file)))
        print("[DEBUG] 当前工作目录: {}".format(os.getcwd()))

    # 解析数据库映射: db_new:db_old 或 db_new.table1:db_old.table2
    try:
        if ':' not in args.mapping:
            raise ValueError("数据库映射格式错误，应为 db_new:db_old 或 db_new.table1:db_old.table2")

        db_new_part, db_old_part = args.mapping.split(':', 1)
        db_new_part = db_new_part.strip()
        db_old_part = db_old_part.strip()

        if not db_new_part or not db_old_part:
            raise ValueError("数据库名不能为空")

        # 解析 db_name.table_name 格式
        db_new_name, db_new_table = (db_new_part.split('.', 1) + [None])[:2]
        db_old_name, db_old_table = (db_old_part.split('.', 1) + [None])[:2]

        # 去除空白字符
        db_new_name = db_new_name.strip() if db_new_name else None
        db_old_name = db_old_name.strip() if db_old_name else None
        db_new_table = db_new_table.strip() if db_new_table else None
        db_old_table = db_old_table.strip() if db_old_table else None

        if not db_new_name or not db_old_name:
            raise ValueError("数据库名不能为空")

    except Exception as e:
        print("[ERROR] 解析数据库映射失败: {}".format(e))
        print("请检查数据库映射参数是否正确，格式应为:")
        print("  - db_new:db_old (对比两个数据库)")
        print("  - db_new.table1:db_old.table2 (对比两个表)")
        # 尝试记录到临时日志文件
        try:
            temp_log_path = os.path.join(os.getcwd(), 'error.log')
            with open(temp_log_path, 'a', encoding='utf-8') as temp_log_file:
                temp_log_file.write(f"[ERROR] 解析数据库映射失败: {e}\n")
                import traceback
                traceback.print_exc(file=temp_log_file)
                temp_log_file.write("\n")
                temp_log_file.flush()
        except:
            pass  # 防止在写入日志文件时出错
        sys.exit(1)

    # 构建连接参数
    db_new_param = Parameter()
    db_old_param = Parameter()

    if args.s:
        db_new_param.build(args.s)
        db_old_param.build(args.s)
    if args.sn:
        db_new_param.build(args.sn)
    if args.so:
        db_old_param.build(args.so)

    db_new_param.name = db_new_name
    db_old_param.name = db_old_name
    db_new_param.table = db_new_table
    db_old_param.table = db_old_table

    if not db_new_param.valid() or not db_old_param.valid():
        print("错误：数据库连接参数不完整")
        print("请检查以下信息是否正确提供:")
        if not db_new_param.valid():
            print(f"  新库连接参数: {db_new_param}")
        if not db_old_param.valid():
            print(f"  旧库连接参数: {db_old_param}")
        print("连接参数格式应为: user:pass@host:port")
        print("例如: root:123456@localhost:3306")
        # 尝试记录到临时日志文件
        try:
            temp_log_path = os.path.join(os.getcwd(), 'error.log')
            with open(temp_log_path, 'a', encoding='utf-8') as temp_log_file:
                temp_log_file.write("[ERROR] 数据库连接参数不完整\n")
                temp_log_file.write(f"新库参数: {db_new_param}\n")
                temp_log_file.write(f"旧库参数: {db_old_param}\n")
                temp_log_file.flush()
        except:
            pass  # 防止在写入日志文件时出错
        sys.exit(1)

    print(f"新库: {db_new_param}")
    print(f"旧库: {db_old_param}")
    print(f"执行模式: {'是' if args.x else '否'}")
    print(f"数据拷贝: {'是' if args.c else '否'}")
    print(f"跳过外键: {'是' if args.no_fk else '否'}")

    # 添加更友好的提示信息
    print("正在连接数据库...")
    # 连接数据库
    try:
        db_new_conn = connect_mysql(db_new_param.host, db_new_param.name, db_new_param.user, db_new_param.pw,
                                    db_new_param.port)
        db_old_conn = connect_mysql(db_old_param.host, db_old_param.name, db_old_param.user, db_new_param.pw,
                                    db_old_param.port)
        if not args.no_redirect:
            print("[DEBUG] 数据库连接成功")
    except Exception as e:
        error_msg = f"[ERROR] 数据库连接失败: {e}"
        print(error_msg)
        # 提供更友好和具体的错误提示
        if "Access denied" in str(e):
            print("[ERROR] 用户名或密码错误")
            print("  请检查以下信息是否正确:")
            print(f"    - 新库用户名: {db_new_param.user}")
            print(f"    - 新库主机: {db_new_param.host}")
            print(f"    - 新库端口: {db_new_param.port}")
            print(f"    - 新库数据库名: {db_new_param.name}")
            print(f"    - 旧库用户名: {db_old_param.user}")
            print(f"    - 旧库主机: {db_old_param.host}")
            print(f"    - 旧库端口: {db_old_param.port}")
            print(f"    - 旧库数据库名: {db_old_param.name}")
            print("  提示: 确保数据库用户具有访问指定数据库的权限")
        elif "Can't connect" in str(e):
            print("[ERROR] 无法连接到数据库服务器")
            print("  请检查以下信息是否正确:")
            print(f"    - 新库主机地址: {db_new_param.host}")
            print(f"    - 新库端口号: {db_new_param.port}")
            print(f"    - 旧库主机地址: {db_old_param.host}")
            print(f"    - 旧库端口号: {db_old_param.port}")
            print("  提示: 确保数据库服务正在运行且网络连接正常")
        elif "Unknown database" in str(e):
            print("[ERROR] 数据库不存在")
            print("  无法找到以下数据库:")
            print(f"    - 新库数据库名: {db_new_param.name}")
            print(f"    - 旧库数据库名: {db_old_param.name}")
            print("  提示: 请确认数据库名称是否正确，或者先创建该数据库")
        else:
            print(f"[ERROR] 请检查连接参数是否正确:")
            print(
                f"  新库: user={db_new_param.user}, host={db_new_param.host}, port={db_new_param.port}, database={db_new_param.name}")
            print(
                f"  旧库: user={db_old_param.user}, host={db_old_param.host}, port={db_old_param.port}, database={db_old_param.name}")

        # 尝试记录到临时日志文件
        try:
            temp_log_file_path = os.path.join(os.getcwd(), 'error.log')
            with open(temp_log_file_path, 'a', encoding='utf-8') as temp_log_file:
                temp_log_file.write(f"{error_msg}\n")
                temp_log_file.write(f"连接参数详情:\n")
                temp_log_file.write(
                    f"  新库: user={db_new_param.user}, host={db_new_param.host}, port={db_new_param.port}, database={db_new_param.name}\n")
                temp_log_file.write(
                    f"  旧库: user={db_old_param.user}, host={db_old_param.host}, port={db_old_param.port}, database={db_old_param.name}\n")
                import traceback
                temp_log_file.write(f"[ERROR] 详细追踪信息:\n")
                traceback.print_exc(file=temp_log_file)
                temp_log_file.write("\n")
                temp_log_file.flush()
        except:
            pass  # 防止在写入日志时出错

        sys.exit(1)

    # 打开输出文件
    try:
        # 添加文件系统调试信息
        diff_dir = os.path.dirname(args.file)
        os.makedirs(diff_dir, exist_ok=True)
        abs_file = os.path.abspath(args.file)
        if not args.no_redirect:
            print(f"DEBUG: 输出目录: {diff_dir}")
            print(f"DEBUG: 绝对路径: {abs_file}")
            print(f"DEBUG: 当前工作目录: {os.getcwd()}")
            print(f"DEBUG: 输出目录是否存在: {os.path.exists(diff_dir)}")
            print(f"DEBUG: 输出目录是否可写: {os.access(diff_dir, os.W_OK)}")

        # 验证文件可写性
        try:
            if not args.no_redirect:
                print(f"DEBUG: 开始文件可写性测试...")
            with open(abs_file, 'w', encoding='utf-8') as test_file:
                test_file.write("-- 测试写入\n")
                test_file.flush()
                # 确保写入磁盘
                test_file.close()
            if not args.no_redirect:
                print(f"DEBUG: 文件可写测试通过: {abs_file}")
            # 验证文件确实存在
            if os.path.exists(abs_file):
                file_size = os.path.getsize(abs_file)
                if not args.no_redirect:
                    print(f"DEBUG: 测试文件已创建: {abs_file} (大小: {file_size} 字节)")
                # 删除测试文件
                os.remove(abs_file)
                if not args.no_redirect:
                    print(f"DEBUG: 测试文件已删除: {abs_file}")
            else:
                print(f"ERROR: 测试文件未创建: {abs_file}")
                raise Exception("测试文件未创建")
        except Exception as e:
            print(f"ERROR: 文件不可写: {e}")
            print(f"[ERROR] 异常类型: {type(e).__name__}")
            print("请检查以下信息:")
            print(f"  - 输出文件路径: {abs_file}")
            print(f"  - 目录是否存在: {os.path.exists(diff_dir)}")
            print(f"  - 目录是否可写: {os.access(diff_dir, os.W_OK)}")
            print("可能的解决方案:")
            print("  1. 检查文件路径是否正确")
            print("  2. 确保目录存在且有写入权限")
            print("  3. 尝试使用其他输出路径")
            import traceback
            traceback.print_exc()
            # 尝试记录到临时日志文件
            if 'temp_log_file' in locals():
                temp_log_file.write(f"ERROR: 文件不可写: {e}\n")
                temp_log_file.write(f"ERROR: 异常类型: {type(e).__name__}\n")
                temp_log_file.flush()
            raise

        # 尝试在其他位置查找
        alt_paths = [
            os.path.join(os.getcwd(), os.path.basename(args.file)),
            os.path.join(os.path.dirname(sys.executable), os.path.basename(args.file)) if getattr(sys, 'frozen',
                                                                                                  False) else None
        ]
        for alt_path in alt_paths:
            if alt_path:
                if not args.no_redirect:
                    print(f"DEBUG: Checking alternative path: {alt_path}")
                if os.path.exists(alt_path):
                    if not args.no_redirect:
                        print(
                            f"File found at alternative location: {alt_path} (size: {os.path.getsize(alt_path)} bytes)")
                    break
                else:
                    if not args.no_redirect:
                        print(f"DEBUG: Alternative path does not exist: {alt_path}")

        # 确保日志目录也存在
        log_dir = os.path.dirname(EXEC_LOG_PATH)
        # 修复：如果log_dir为空（例如EXEC_LOG_PATH是相对路径），则使用当前目录
        if log_dir and not os.path.exists(log_dir):
            os.makedirs(log_dir, exist_ok=True)
        if not args.no_redirect:
            print(f"DEBUG: 日志文件路径: {EXEC_LOG_PATH}")
            print(f"DEBUG: 日志目录: {log_dir}")
            print(f"DEBUG: 开始写入输出文件: {abs_file}")

        with open(args.file, 'w', encoding='utf-8') as diff_file, open(EXEC_LOG_PATH, 'w',
                                                                       encoding='utf-8') as log_file:
            if not args.no_redirect:
                print(f"DEBUG: 成功打开输出文件: {args.file}")
                print(f"DEBUG: 成功打开日志文件: {EXEC_LOG_PATH}")
            try:
                # 开始事务
                db_old_conn.begin()

                # 1. 关闭外键检查（如果未跳过）
                if not args.no_fk:
                    disable_fk = "SET FOREIGN_KEY_CHECKS=0;"
                    write_sql_to_file(diff_file, "关闭外键检查", [disable_fk])
                    if args.x:
                        with db_old_conn.cursor() as cur:
                            cur.execute(disable_fk)

                # 2. 执行重命名
                # 初始化变量以避免UnboundLocalError
                rename_parsers = []
                pipe_parsers = []

                rename_sqls = []
                for p in rename_parsers:
                    rename_sqls.extend(p.build_rename_sql())
                write_sql_to_file(diff_file, "表/字段重命名", rename_sqls)
                if args.x:
                    execute_sql(db_old_conn, rename_sqls, log_file)

                # 3. 解析 map.config
                rename_parsers, pipe_parsers = parse_map_config(MAP_CONFIG_PATH, db_old_conn, db_new_conn,
                                                                args.no_redirect)

                # 4. 外键处理（如果未跳过）
                if not args.no_fk:
                    drop_fk_sqls, add_fk_sqls = build_foreign_key_sqls(db_new_conn, db_new_param.name, db_old_conn,
                                                                       db_old_param.name)
                    write_sql_to_file(diff_file, "删除外键", drop_fk_sqls)
                    write_sql_to_file(diff_file, "新增外键", add_fk_sqls)
                    if args.x:
                        execute_sql(db_old_conn, drop_fk_sqls, log_file)
                        execute_sql(db_old_conn, add_fk_sqls, log_file)
                else:
                    # 明确提示跳过外键
                    write_sql_to_file(diff_file, "跳过外键操作 (--no-fk)",
                                      ["-- 注意：由于使用了 --no-fk 参数，外键增删操作已被跳过。"])

                # 5. 表结构同步
                # 获取新库所有表
                with db_new_conn.cursor() as cur:
                    if db_new_param.table:
                        # 如果指定了表，则只对比该表
                        tables = [db_new_param.table]
                    else:
                        cur.execute("SHOW TABLES")
                        tables = [row[0] for row in cur.fetchall()]

                create_table_sqls = []
                alter_table_sqls_all = []  # 用于收集所有 ALTER TABLE 语句

                for tbl in tables:
                    old_tbl = tbl
                    # 检查是否被重命名
                    for p in rename_parsers:
                        if p.old_table == tbl:
                            old_tbl = p.new_table
                            break

                    if not exists_table(db_old_conn, old_tbl):
                        # 新建表
                        create_sql = get_table_structure(db_new_conn, tbl)
                        create_table_sqls.append(create_sql)
                        # 拷贝数据（即使不执行也生成INSERT语句）
                        if args.c:
                            with db_new_conn.cursor(pymysql.cursors.DictCursor) as cur:
                                cur.execute(f"SELECT * FROM `{tbl}`")
                                rows = cur.fetchall()
                                if rows:
                                    cols = ', '.join([f"`{k}`" for k in rows[0].keys()])
                                    # 修复：确保每行数据都被括号包围
                                    vals = ", ".join([q(', '.join([safestr(v) for v in r.values()])) for r in rows])
                                    insert_sql = f"INSERT INTO `{old_tbl}` ({cols}) VALUES {vals};"
                                    create_table_sqls.append(insert_sql)
                    else:
                        # 结构变更
                        new_fields = get_table_columns(db_new_conn, tbl)
                        old_fields = get_table_columns(db_old_conn, old_tbl)
                        alter_sqls = build_alter_table_sql(new_fields, old_fields)
                        if alter_sqls:
                            # 修复：将复杂的 ALTER TABLE 分解为多个简单的 ALTER TABLE 语句以便执行
                            for single_alter in alter_sqls:
                                alter_table_sqls_all.append(f"ALTER TABLE `{old_tbl}` {single_alter};")

                write_sql_to_file(diff_file, "新建表", create_table_sqls)
                # 写入分解后的 ALTER TABLE 语句
                write_sql_to_file(diff_file, "修改表结构", alter_table_sqls_all)

                # 6. 执行 SQL（如果指定了 -x）
                if args.x:
                    execute_sql(db_old_conn, create_table_sqls, log_file)
                    # 执行分解后的 ALTER TABLE 语句
                    execute_sql(db_old_conn, alter_table_sqls_all, log_file)

                # 6. 数据迁移（=>）
                pipe_sqls = []
                for p in pipe_parsers:
                    pipe_sqls.extend(p.build_pipe_sql(db_old_conn, db_new_conn))
                write_sql_to_file(diff_file, "数据迁移", pipe_sqls)
                if args.x:
                    execute_sql(db_old_conn, pipe_sqls, log_file)

                # 7. 启用外键（如果未跳过）
                if not args.no_fk:
                    enable_fk = "SET FOREIGN_KEY_CHECKS=1;"
                    write_sql_to_file(diff_file, "启用外键检查", [enable_fk])
                    if args.x:
                        with db_old_conn.cursor() as cur:
                            cur.execute(enable_fk)
                else:
                    write_sql_to_file(diff_file, "跳过启用外键检查 (--no-fk)",
                                      ["-- 注意：由于使用了 --no-fk 参数，未重新启用外键检查。"])

                # 提交事务
                db_old_conn.commit()
                print("Database sync completed, transaction committed.")
                print(f"Difference SQL has been generated: {args.file}")

                # 验证文件是否真的被创建
                final_abs_path = os.path.abspath(args.file)
                if not args.no_redirect:
                    print(f"DEBUG: Verifying file existence: {final_abs_path}")
                if os.path.exists(final_abs_path):
                    file_size = os.path.getsize(final_abs_path)
                    if not args.no_redirect:
                        print(f"File verification succeeded: {final_abs_path} (size: {file_size} bytes)")

                    # 尝试读取文件内容以验证完整性
                    try:
                        with open(final_abs_path, 'r', encoding='utf-8') as f:
                            content = f.read()
                        if not args.no_redirect:
                            print(f"DEBUG: File content length: {len(content)} characters")
                            print(f"DEBUG: First 100 characters of file: {content[:100]}")
                    except Exception as e:
                        if not args.no_redirect:
                            print(f"WARNING: Unable to read file content: {e}")
                else:
                    if not args.no_redirect:
                        print(f"File verification failed: {final_abs_path} (file does not exist)")
                return  # 成功执行后直接返回

            except Exception as e:
                db_old_conn.rollback()
                print(" Operation failed, rolled back.")
                import traceback
                traceback.print_exc()
                try:
                    log_file.write(f"Exception: {e}\n")
                except:
                    pass
                # 确保异常信息被打印
                print(f"[ERROR] Exception occurred during execution: {e}")
                import traceback
                traceback.print_exc()
                # 尝试记录到临时日志文件
                if 'temp_log_file' in locals():
                    temp_log_file.write(f"[ERROR] Exception occurred during execution: {e}\n")
                    temp_log_file.flush()

    finally:
        # 确保数据库连接被关闭
        try:
            if 'db_new_conn' in locals() and db_new_conn and db_new_conn.open:
                db_new_conn.close()
        except Exception as e:
            print(f"[ERROR] 关闭新库连接时发生异常: {e}")

        try:
            if 'db_old_conn' in locals() and db_old_conn and db_old_conn.open:
                db_old_conn.close()
        except Exception as e:
            print(f"[ERROR] 关闭旧库连接时发生异常: {e}")


if __name__ == '__main__':
    main()

